/*
 * Copyright 2010-2012 Susanta Tewari. <freecode4susant@users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
* GLikeliHood.java
*
* Created on November 22, 2007, 9:10 AM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
 */
package genomemap.worker;

import db.genomemap.G_DBDataSource;

import genomemap.data.DataSource;
import genomemap.data.GDataSource;
import genomemap.data.RFLPImputation;
import genomemap.stat.PValue;
import genomemap.stat.StandardError;
import genomemap.worker.helper.GHoodHelper;
import genomemap.worker.helper.MyInteger;

import gui.genomemap.event.event.WorkerOutputEvent;
import gui.genomemap.inputModel.ComGLHoodIModel;

import javautil.collections.ArrayUtil;
import javautil.io.LogUtil;

import javax.swing.SwingWorker;

/**
 * This is a positive-log likelihood.
 * @author stewari1
 */
public class GLikeliHood extends LHood {


    // properties

    /** Field description */
    private int iterationLimit = 150;

    /** Field description */
    private double convgEpsilon = 0.0001;

    /** Field description */
    private boolean converged = false;

    /** Field description */
    private double[] cProbInitial = null;

    /** Field description */
    private double[] cProbEst = null;

    /** Field description */
    private double[] cellProbEst = null;

    /** Field description */
    private double[][] iterationHistory = null;

    /** Field description */
    private boolean iterationHistoryKept = false;

    /** Field description */
    private String iterationLog = "";

    /** Field description */
    private GDataSource gData = null;

    /**
     * Constructs ...
     */
    public GLikeliHood() {}

    /**
     * sets the runtime properties of GLhood from the model.
     * @param inputModel
     */
    public GLikeliHood(ComGLHoodIModel inputModel) {

        gData = DataSource.getGDataSource(inputModel.getLinkageGroup(), inputModel.getOrder());

        gData.setRflpImputation(inputModel.getRflpImputation());
        setGData(gData);
        setConvgEpsilon(inputModel.getConvgEpsilon());
        setIterationLimit(inputModel.getIterationLimit());

        if (inputModel.getOptionalOperModel().isComputingSE()) {
            setIterationHistoryKept(true);
        }
    }

    /**
     * Method description
     */
    private static void runSwingWorker() {

        SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {

            public Void doInBackground() {

                int[] order          = { 1, 2, 3, 4, 5 };
                G_DBDataSource gData = new G_DBDataSource(1, order);

                gData.loadData();

                GLikeliHood gHood = new GLikeliHood();

                gHood.setGData(gData);
                gHood.setVerbose(true);


//              try {
//
//              } catch (Exception ex) {
//                  ex.printStackTrace();
//              } finally {
//                  System.out.println("done");
//              }
                gHood.runGHood();

                return null;
            }
        };

        worker.execute();
    }

    public static void main(String[] args) {

        int[] order       = { 13, 28, 33, 36, 22 };
        GLikeliHood gHood = new GLikeliHood();
        GDataSource gData = DataSource.getGDataSource(5, order);

        gData.setRflpImputation(new RFLPImputation(RFLPImputation.POLICY_PROB));
        gHood.setGData(gData);
        gHood.setIterationLimit(30);
        gHood.setVerbose(true);
        gHood.runGHood();
        LogUtil.print(gHood.getCProbEst());
    }

    public double runGHood() {

        setStatusInfo("Genetic Likleihood Computation has Started");

        /**
         * IMPORT NECESSARY DATA FROM Gdata TO COMPUTE THE GENETIC LIKELIHOOD
         */
        int loci   = gData.getLoci();
        int height = 3;

        if (loci < 3) {

            System.out.println("Loci is too Small: Loci: " + loci);
            System.out.println("Genetic Likelihood can`t be run..");
            System.exit(0);
        }


        // using local copy of order and data
        int[][] data          = gData.getdata().clone();
        int[][] genotype      = gData.getArrayOfDistinctGenotypes(data);
        int distinctGenotypes = genotype[0].length;
        int[] genotypeFreq    = gData.getArrayOfCellFreq(data);

        /**
         * if cProbInitial isn`t set for this Likelihood calc. value
         * is computed from the data: gData. This provides for tweaking
         * cProbs and run Likelihood independently.
         */
        double[] cProbOld = null;

        if (cProbInitial == null) {
            cProbOld = gData.getInitialCProb(data);
        } else {
            cProbOld = cProbInitial.clone();
        }

        /**
         * CREATING activeKFirst,cArrayFirst,linkInfoFirst,kArrayFirst
         */
        MyInteger activeKFirstIntObj = new MyInteger(0);

        GHoodHelper.getActiveXoverFirst(height, genotype, activeKFirstIntObj);

        int activeKFirst      = activeKFirstIntObj.value;
        int[][] kArrayFirst   = new int[activeKFirst][height - 1];
        int[] linkInfoFirst   = new int[activeKFirst];
        int[][][] cArrayFirst = new int[activeKFirst][distinctGenotypes][4];

        GHoodHelper.getInfo4First(height, genotype, kArrayFirst, linkInfoFirst, cArrayFirst);

        /**
         * CREATING activeKLast,cArrayLast,kArrayLast
         */
        int[] activeKLast = new int[6];

        GHoodHelper.getActiveXoverLastArray(height, genotype, activeKLast);

        int[][][] kArrayLast   = new int[6][][];
        int[][][][] cArrayLast = new int[6][][][];

        for (int i = 0; i < 6; i++) {

            kArrayLast[i] = new int[activeKLast[i]][height - 1];
            cArrayLast[i] = new int[activeKLast[i]][distinctGenotypes][4];
        }

        GHoodHelper.getInfo4Last(height, genotype, kArrayLast, cArrayLast);

        /**
         * CREATING spores,tempSumIndex.
         */
        int[][] spores = GHoodHelper.getSpores();
        int[][][][] tempSumIndex = GHoodHelper.getTempSumIndex(distinctGenotypes, spores,
                                       activeKLast, cArrayLast);

        /**
         * COMPUTE THE LIKELIHOOD.
         */
        for (int iteration = 0; iteration < iterationLimit; iteration++) {

            setProgress((int) ((double) iteration / iterationLimit * 100));

            /**
             * GETTING THE cProbNew.
             */
            double[] probFOld = new double[distinctGenotypes];
            double[] cProbNew = GHoodHelper.getCProbNew(loci, height, distinctGenotypes, genotype,
                                    genotypeFreq, cProbOld, activeKFirst, kArrayFirst,
                                    linkInfoFirst, cArrayFirst, activeKLast, kArrayLast,
                                    cArrayLast, spores, tempSumIndex, probFOld);

            /**
             * REPORTING THE LIKELIHOOD.
             */
            double logLikelihood = 0.0;

            for (int index = 0; index < distinctGenotypes; index++) {

                logLikelihood += genotypeFreq[index] * Math.log(probFOld[index]);
            }

            setLhood(logLikelihood);

            /**
             * FIRE WorkerOutputEvent and Record iteration result
             */
            fireWorkerOutputEvent(new WorkerOutputEvent(this, 0));

            String iterationResult = "The log-likelihood is " + logLikelihood + " at iteration "
                                     + iteration;

            setStatusInfo(iterationResult);
            setIterationLog(getIterationLog() + (iterationResult + "\n"));

            if (isVerbose()) {
                System.out.println(iterationResult);
            }

            /**
             * BOOKKEEPING ITERATION HISTORY. NEEDED FOR COMPUTING STANDARD ERRORS
             */
            if (isIterationHistoryKept()) {

                if (iteration == 0) {
                    iterationHistory = ArrayUtil.addRow(cProbNew);
                } else if (iteration > 0) {
                    iterationHistory = ArrayUtil.addRow(iterationHistory, cProbNew);
                }
            }

            /**
             * CHECK CONVERGENCE.
             */
            if (GHoodHelper.Convergence(cProbOld, cProbNew, getConvgEpsilon()) == 1) {

                if (isVerbose()) {
                    System.out.println(" EM Convergence Achieved !");
                }

                this.cProbEst    = cProbOld.clone();
                this.cellProbEst = probFOld.clone();

                setConverged(true);

                if (iteration == 0) {
                    System.out.println("Converged in one iteration");
                }

                setProgress(100);

                break;
            }

            cProbOld      = cProbNew.clone();
            this.cProbEst = cProbNew.clone();
        }

        return getLhood();
    }

    public double getPValue() throws Exception {

        double pValue = -1.;

        setStatusInfo("Computing the P Value ...");

        pValue = PValue.getPValue(getCProbEst(), gData.getOrder(), gData.getdata(), isVerbose());

        setStatusInfo("P Value: " + pValue);

        return pValue;
    }

    public double[] getStdErrs() throws Exception {

        setStatusInfo("Computing the Standard Errors...");

        double[] stdErrs = StandardError.getStdErrs(this, isVerbose());

        setStatusInfo("");

        return stdErrs;
    }

    public double[] getRecom() {

        double[] result = new double[cProbEst.length];

        for (int index = 0; index < cProbEst.length; index++) {

            result[index] = cProbEst[index] * (1 - cProbEst[index] / 2.0);
        }

        return result;
    }

    /**
     * computes the function which is used for simulated annealing
     * returns negative of log-likelihood used in simulated annealing
     * (for minimization) which would give rise to an order with maximum
     * log-likelihood(thus max. likelihood)
     */
    @Override
    public double compute() {

        getGData().setOrder(getOrder());

        return -runGHood();
    }

    double getConvgEpsilon() {
        return convgEpsilon;
    }

    public int getIterationLimit() {
        return iterationLimit;
    }

    /**
     *
     * @param newVal
     */
    public void setConvgEpsilon(double newVal) {
        convgEpsilon = newVal;
    }

    /**
     *
     * @param newVal
     */
    public void setIterationLimit(int newVal) {
        iterationLimit = newVal;
    }

    /**
     *
     * @param newVal
     */
    void setIterationHistoryKept(boolean newVal) {
        iterationHistoryKept = newVal;
    }

    public double[] getCellProbEst() {
        return cellProbEst;
    }

    public double[] getCProbEst() {
        return cProbEst;
    }

    public double[] getCProbInitial() {
        return cProbInitial;
    }

    public GDataSource getGData() {
        return gData;
    }

    public boolean isConverged() {
        return converged;
    }

    boolean isIterationHistoryKept() {
        return iterationHistoryKept;
    }

    /**
     *
     * @param newVal
     */
    public void setCellProbEst(double[] newVal) {
        cellProbEst = newVal;
    }

    /**
     *
     * @param newVal
     */
    void setConverged(boolean newVal) {
        converged = newVal;
    }

    /**
     *
     * @param newVal
     */
    public void setCProbEst(double[] newVal) {
        cProbEst = newVal;
    }

    /**
     *
     * @param newVal
     */
    public void setCProbInitial(double[] newVal) {
        cProbInitial = newVal;
    }

    /**
     *
     * @param newVal
     */
    @Override
    public void setGData(GDataSource gData) {

        this.gData = gData;

        setOrder(gData.getOrder());
    }

    public double[][] getIterationHistory() {
        return iterationHistory;
    }

    public void setIterationHistory(double[][] iterationHistory) {
        this.iterationHistory = iterationHistory;
    }

    public String getIterationLog() {
        return iterationLog;
    }

    void setIterationLog(String iterationLog) {
        this.iterationLog = iterationLog;
    }

    @Override
    public double initialValue() {
        return compute();
    }

    @Override
    public double getLogLikeliHood() {
        return getLhood();
    }
}    // ends the class

